home *** CD-ROM | disk | FTP | other *** search
/ Cream of the Crop 25 / Cream of the Crop 25.iso / bbs / doorinfo.zip / DOORUTIL.MAN < prev    next >
Text File  |  1997-05-14  |  74KB  |  1,564 lines

  1.  
  2.                         FALKEN Programmers Guide
  3.                             by Chris Whitacre
  4.                        Copywrite BC Software 1997
  5.                    Can be used for Falken 7.07 or 9.x
  6.  
  7. NOTE: Any other info if needed can be obtained by either emailing
  8. cd@midnight.kingsnet.com, telnet midnight.kingsnet.com, or calling
  9. (209) 582-4747.
  10.  
  11.     What It Is.
  12.     -----------
  13.  
  14.         This document contains programming information for Falken BBS. This
  15.         documentation is actually little more than a collection of notes
  16.         that have been gathered together into more or less a logical
  17.         presentation on the subject of Falken doors.
  18.  
  19.         Falken is a multi-line BBS package that runs under DOS on IBM PC and
  20.         compatible computers, 80386 and above.  It can support up to 64
  21.         modems simultaneously.
  22.  
  23.         External modules, called DOORS in other BBS jargon, are supported.
  24.         Because Falken contains a multitasker and a Fossil-compatible
  25.         multi-line serial driver, most DOORS for single-line BBS packages
  26.         will not run correctly under Falken. That is because the programmers
  27.         for those doors often do not use the existing serial drivers, but
  28.         install their own hardware interrupt functions.  Also, many of them
  29.         reprogram the computer's clock, and write directly to the video
  30.         screen, and many other things that are fine for a one-line BBS to
  31.         do, but are not fine for multi-line BBSes to do.
  32.  
  33.         Remember that for Falken to work correctly, the BBS program MUST
  34.         retain control of all the hardware and all the interrupts, so that
  35.         it can service all of the modems.
  36.  
  37.         A door may be started from the Main Menu of Falken, or from any of
  38.         the sub-menus.  Simply place the name of the program to execute in
  39.         the 'action' field of the menu screen in BBSCFG.  Falken internal
  40.         functions such as email and files are identified with a '@' in the
  41.         first column, i.e. @EMAIL or @TLCF.  If the '@' is not in the first
  42.         column, then Falken assumes the entry to be the name of a door
  43.         program to execute.
  44.  
  45.         If you write a door program for Falken BBS systems, and choose to
  46.         place it in the public domain, or distribute it as shareware, you
  47.         may forward a copy to me to be included with Falken updates and
  48.         purchases.  All programs received by me will be treated as either
  49.         public domain or shareware, as indicated by the author.  Not all
  50.         programs that are received will be distributed by me, but all will
  51.         be placed on our customer support BBS for access by Falken sysops.
  52.  
  53.  
  54.         So, here is a programming API and a set of guidelines for writing
  55.         games and utilities for Falken BBS.
  56.  
  57.         I believe that writing games for Falken is easier than writing games
  58.         for other BBSes.  Particularly for other multi-line BBSes. I have
  59.         written a set of functions that performs all of the difficult stuff.
  60.         Getting input from the user, sending text to the user, broadcasting
  61.         to multiple lines, and many other functions are provided in simple
  62.         function calls.  Indeed, there is even a printf()-like function for
  63.         sending formatted output to the modem.
  64.  
  65.         If you would like specific information on writing or porting
  66.         programs to Falken, contact me at the addresses listed below.
  67.  
  68.     How To Use This Document.
  69.     -------------------------
  70.  
  71.         First, read it completely.  All the way through.  Some things that
  72.         appear unclear at first are explained in later sections of the
  73.         document.  As you see more and more examples of code, and more
  74.         descriptions of how things work, the details will become more clear.
  75.  
  76.         Next, look at the example programs that are provided.  I have
  77.         included 3 very important programs with this document.  They are :
  78.  
  79.             zmsend.c - full source code for a Zmodem transfer program
  80.             tlcfdemo - full source code for a multi-user chat area
  81.             playxwit.c - full source code for a program loader for
  82.                        croswits, a multiplayer word game.
  83.  
  84.         These programs demonstrate some of the really useful methods for
  85.         writing games for Falken - direct I/O via Fossil calls, multiplayer
  86.         games, and database usage.
  87.  
  88.         Next, write something.  I firmly believe that the way to master any
  89.         language or API is to jump in and DO IT.  The calls are easy, and it
  90.         really is the best way to learn.
  91.  
  92.     Legal Stuff.
  93.     ------------
  94.  
  95.         All the information included in this package is correct, to the best
  96.         of my knowledge, as of 08-15-95.  All the information that is
  97.         presented here, and all source code presented in this package, is
  98.         made freely available by me, Herb Rose.  I am the sole owner of this
  99.         document and the source code that is included.  I give my permission
  100.         for anyone to use the information and code herein for any purpose
  101.         that they see fit.  I offer no warranty of any kind, nor any
  102.         guarantee of correctness, completeness, or fitness for any
  103.         application.  In other words - here it is, use it if you wish, but
  104.         use it at your own risk.
  105.  
  106.         If any errors or omissions are found in this document, or if you
  107.         desire further information or clarification, I can be reached at
  108.         rose@dgsys.com via internet, or by sending email to 'herb' on The
  109.         Doctor's Office BBS at 703-749-2860.
  110.  
  111.         If you are having trouble getting something to work, PLEASE send me
  112.         the offending source code.  I will not answer letters that state
  113.         "such-and-such function does not work for me" unless you send the
  114.         source code with it.  Trust me when I say that I use ALL these
  115.         functions in my programs, and they work as presented.  I will keep
  116.         all such source code confidential, and will probably erase it as
  117.         soon as I determine the problem, anyway.
  118.  
  119.     General Notes concerning doors.
  120.     -------------------------------
  121.  
  122.         First, since your door program will not have control of the PC, and
  123.         since it must share resources with Falken and with other door
  124.         programs, some popular PC programming techniques must not be used.
  125.  
  126.         First and foremost, your door must never attempt to control any of
  127.         the PC interrupt vectors or hardware.  It must not install its own
  128.         serial interrupt handler.  It must not write directly to the display
  129.         screen. It must not read the keyboard for input.  All hardware and
  130.         interrupts must be under control of Falken, or nothing will work
  131.         correctly.
  132.  
  133.         Second, your door must be as small as possible.  In general, try to
  134.         keep your program from using more than 256K memory.  This can be
  135.         difficult at times, but it can usually be done.  Keep large arrays
  136.         on disk, rather than in memory.  Do not load in large data files.
  137.         Cut corners wherever you can to conserve space.  Remember that
  138.         Falken is very large, and your program must fit into whatever memory
  139.         is left over after Falken is loaded.
  140.  
  141.         Third, Falkens multitasker does not support overlays, so you cannot
  142.         use overlays in your program in any form.
  143.  
  144.         Fourth, the Falken multitasker provides a scheduler which allows the
  145.         different active programs to run in round-robin fashion.  Whenever
  146.         your program is non-productive, i.e. waiting for the user to press a
  147.         key or for a block of input to be ready for processing, you should
  148.         use the RELINQ() or DELAY() calls to remove the program from
  149.         execution, and let another task run for a while.  The time slice
  150.         interval is 18.2 times per second under Falken, which means your
  151.         program could be just sitting in a loop for 1/18 second, doing
  152.         nothing, while other programs are waiting their turn to do some
  153.         work.  Be conservative with system resources, so that the overall
  154.         Falken system does not degrade because of your program.
  155.  
  156.  
  157.         Call init() to load up the global variables and to check that the
  158.         BBS is up and running before you do anything else.
  159.  
  160.         Use the standard functions that are provided for communication
  161.         between processes and between your task and the BBS. (see below).
  162.  
  163.         The door program should continually monitor its input queue to
  164.         handle special commands that Falken may send to it.  If the
  165.         'terminate' command is received, the door should stop processing and
  166.         exit. The qgets() and tqgets() functions monitor for the terminate
  167.         event, but if your door does not use these functions, it must
  168.         monitor for the events explicitly.
  169.  
  170.         If you require character-by-character control of the serial port, or
  171.         do not wish for Falken to perform text-related character
  172.         translations, you will need to request control of the serial port.
  173.         Use the setbinarymode() function to gain control of the serial port.
  174.         Falken will disconnect from the serial port, leaving your door free
  175.         to manage the port directly.  Note that when I say 'directly', I do
  176.         not mean at the hardware level.  I mean that your task now has the
  177.         responsibility to control the port using FOSSIL calls via INT 14H.
  178.         See the accompanying FOSSIL documentation for more information on
  179.         this.
  180.  
  181.  
  182.     Rules for writing doors.
  183.     ------------------------
  184.  
  185.         Don't mess with lines that you don't own.
  186.         Don't mess with memory you don't own.
  187.         Don't mess with any hardware or interrupts, EVER.
  188.         Don't change the value of 'who' unless you're sure of the outcome.
  189.         If you open any files or databases, close them when you're done.
  190.         Say 'please' and 'thank you' often.
  191.         Go to the potty before a long car trip.
  192.  
  193.     Sample Code.
  194.     ------------
  195.  
  196.         I am including sample code, including some working doors that I have
  197.         written specifically for that purpose, with this package.  Among the
  198.         most useful of these samples are the files LOADTLCF.C and
  199.         TLCFDEMO.C. These programs comprise a multi-user teleconference
  200.         program, and can be the basis for multi-player games or other
  201.         programs.  These programs use the method of having a small 'loader'
  202.         program run first.  This 'loader' program checks to see if the real
  203.         program is already active, and if so just suspends itself.  If the
  204.         real program is not already active, the loader program first loads
  205.         it, then suspends itself. The real program then monitors all lines
  206.         to see which ones are active for it, and handles the input and
  207.         output for those lines.  This is done in a large loop in the main()
  208.         function.  Almost all the current multi-player games use this
  209.         approach.
  210.  
  211.         I am also including the complete source code for the CROSWITS loader
  212.         program, because it contains a lot of database access code via the
  213.         btrv() functions.  The PLAYXWIT.C file is for study purposes only,
  214.         and will not compile because I have not included some of the header
  215.         files.
  216.  
  217.     Header Files.
  218.     -------------
  219.  
  220.         There are several header (.h) files included with this package.
  221.         The more important ones are :
  222.             structs.h - contains account record structures, and the
  223.                 user_rec structure used to store information about
  224.                 the caller's current status.
  225.             doorutil.h - contains prototypes for the functions, and
  226.                 contains definitions for the various variables and
  227.                 structures used by door programmers.
  228.             bbscfg.h - contains the structure definition for the
  229.                 cfg_rec structure.  Usually, a programmer does not
  230.                 need this information, but if you do need it, there
  231.                 it is.
  232.             tcb.h - structure definitions and constants used by CSWITCH,
  233.                 the multitasking engine for Falken.
  234.  
  235.         Doorutil.h #includes structs.h and bbscfg.h so you only need
  236.         '#include "doorutil.h"' in your program.  If you need access to the
  237.         CSWITCH structures (rarely) then include tcb.h also.
  238.  
  239.     Object Files.
  240.     -------------
  241.  
  242.         The following object files are supplied, in Microsoft C format. All
  243.         .obj files were compiled in LARGE model, with structure packing,
  244.         meaning that all variables are aligned on byte boundaries, not
  245.         aligned on the nearest word or dword boundary.
  246.  
  247.         doorutil.obj - contains all the functions described below.
  248.         lmtca.obj - contains interface functions for CSWITCH API.
  249.         fsubs.obj - contains interface functions for the Fossil driver API.
  250.  
  251.         You must link with doorutil.obj and lmtca.obj. If you will be using
  252.         the fossil calls, then link with fsubs.obj also.
  253.  
  254.         I have also included the source for lmtca.obj (in MASM 5.1) and
  255.         fsubs.obj (in MASM 5.1).
  256.  
  257.     Global Variables.
  258.     -----------------
  259.  
  260.         The variables :
  261.  
  262.             int who;        /* the BBS line that started the door */
  263.             int numlines;   /* number of lines configured  */
  264.             int inq;        /* message queue number to read messages from */
  265.             int outq;       /* message queue number to send messages to BBS */
  266.             struct acct_rec *acct;
  267.                             /* address of array of acct_rec structures */
  268.             struct user_rec *user;
  269.                             /* address of array of user_rec structures */
  270.             struct acct_rec *myacct;    /* pointer to the account record for */
  271.                                         /* the user that started the door. */
  272.                                         /* equivalent to (*acct)[who] */
  273.             struct user_rec *myuser;    /* pointer to the user_rec record for */
  274.                                         /* the user that started the door. */
  275.                                         /* equivalent to (*user)[who] */
  276.             struct bbscfg_rec *cfg;     /* pointer to the bbscfg record */
  277.  
  278.         are initialized in the init() function, and should not be altered.
  279.         The only exception is the 'who' variable, which may be altered to
  280.         cause the output from qprintf() et al, to go to a line other than
  281.         the one that started the door.
  282.  
  283.     Communications with the BBS.
  284.     ----------------------------
  285.  
  286.         All communications with the BBS are performed via message queues and
  287.         a shared memory structure.  As long as you use the functions
  288.         outlined here, all that is done for you, by magic.
  289.  
  290.         The global variable 'who' is used in several of the following
  291.         functions, notably qgets(), send(), qputs(), qprintf() to send
  292.         output to the line that started the door.  Multi-player games, etc,
  293.         may want output to go to lines other than the original starter.  See
  294.         functions sendtoline() and broadcast() to do this.  Alternatively,
  295.         you can change the value of who prior to calling one of the other
  296.         functions that use it to determine destination.
  297.  
  298.         Other functions, such as setbinarymode(), expect a line number as an
  299.         input parameter.  Normally, you will use the 'who' variable, since
  300.         you will be performing these functions for the line that started the
  301.         door, so the call would be ' setbinarymode(who);'.
  302.  
  303.  
  304.         The following conventions will be used to pass information to and
  305.         from the MAINBBS task and subtasks :
  306.  
  307.         When the subtask (your door) starts, it will read a message from
  308.         inter-task message queue #1.  This message will contain all the
  309.         information the subtask requires to execute properly.  It will
  310.         contain the following information, in this format :
  311.  
  312.           struct init_msg
  313.           {
  314.             int type;       /* will always be 7 */
  315.             int line;       /* the line number for this task (0-5) */
  316.             int input_q;    /* the queue to read incoming data from */
  317.             int output_q;   /* the queue to send outgoing data to */
  318.             acct_rec far *acctptr;
  319.             user_rec far *userptr;
  320.             int numlines;   /* how many lines are configured */
  321.             struct cfg_rec far *cfg; /* pointer to BBSCFG record */
  322.           } init;
  323.  
  324.         The acctptr and userptr are pointers to an array of structures. They
  325.         are always FAR pointers, meaning they contain both segment and
  326.         offset values.  If you use the small memory model to compile your
  327.         program, you must explicitly declare these pointers as FAR.
  328.  
  329.         They should be accessed as follows :
  330.  
  331.             To get the user's name from the account record :
  332.                init.acctptr[line].acctname  /* acctname is a char array */
  333.  
  334.         Refer to the STRUCTS.H file for the format of the user record and
  335.         the account record.
  336.  
  337.         All messages received after this initial message will be read from
  338.         the task's input_q.  All messages your task wants to send to the
  339.         user must be sent via your tasks output_q.
  340.  
  341.         Use the functions in DOORUTIL.C to communicate with Falken, rather
  342.         than using the message queues directly.  All functions which are
  343.         available to a door program have already been included in DOORUTIL.
  344.  
  345.         NOTE: The time and date fields in account records use a special
  346.               format used to measure the current date from a fixed
  347.               reference, in this case 1 Jan 1900.  A C function is supplied
  348.               to translate dates to/from a string or to 3 integers (month,
  349.               day, year).
  350.  
  351.  
  352.     Function Calls.
  353.     ---------------
  354.  
  355.         You should read the documentation for CSWITCH, the multitasking
  356.         functions used by Falken.  Functions to control message passing,
  357.         semaphores, loading new tasks, and preventing task-switching from
  358.         occuring at critical times, are all provided.
  359.  
  360.         The following function calls are available to all doors via doorutil.c
  361.  
  362.         int isinstalled(void)
  363.  
  364.             This function is called by init(), and it checks to see if the
  365.             Falken BBS is running.  If not, a message is printed, and the
  366.             door exits.
  367.  
  368.         int init (void)
  369.  
  370.             init() performs all the doors initialization required, including
  371.             attaching to the common memory area for acct and user
  372.             structures, loading the global variables with the correct data,
  373.             and checking that Falken is up and running.
  374.  
  375.             The return value is the line number (global variable 'who') that
  376.             the door is started for.
  377.  
  378.         void logoff (int line)
  379.  
  380.             Logs off the user on the specified line.
  381.  
  382.         void setbinarymode (int line)
  383.  
  384.             This function tells Falken to put the line into binary mode.
  385.             Binary mode means that your door is now responsible for reading
  386.             and writing to the user's modem.  Fossil calls must then be used
  387.             to communicate with the user's line.
  388.  
  389.             If you don't know what that means, don't use this call!
  390.  
  391.         void settextmode (int line)
  392.  
  393.             Puts the line back in 'text' mode, where Falken processes user
  394.             input and makes it available via the qgets() functions.
  395.  
  396.             This is the normal mode of the line when the door runs, and
  397.             under most circumstances, there is no need to change it.
  398.  
  399.         void clear_input_buffer (int line)
  400.  
  401.             Causes Falken to discard all data in the line's input buffer.
  402.  
  403.         void clear_output_buffer (int line)
  404.  
  405.             Causes Falken to discard all data in the line's output buffer.
  406.  
  407.         void get_port (int line)
  408.  
  409.             Use this function to gain control of a specific line from
  410.             Falken, for instance, to dial to another BBS.  The line will be
  411.             given to your program in binary mode.
  412.  
  413.         void return_port (int line)
  414.  
  415.             Return a line, obtained with get_port(), to Falken.
  416.  
  417.         int get_oba (void)
  418.  
  419.             Returns the number of bytes available in the output buffer for
  420.             line 'who'.  This is 16384-(bytes in buffer).
  421.  
  422.             If you wish to call this function for a line other than the line
  423.             that started the door, change the value of 'who' before calling
  424.             this function.
  425.  
  426.         int get_in_cnt(void)
  427.  
  428.             get_in_cnt() returns the number of bytes waiting in the user's
  429.             input buffer.  Falken will buffer the input until the user has
  430.             pressed ENTER, at which time the entire buffer will be sent to
  431.             the door on the message queue.
  432.  
  433.         int bbslog (char *s)
  434.  
  435.             Writes the string pointed to by 's' to the bbslog.txt file and
  436.             to the log window of the BBS monitor.
  437.  
  438.         void lprintf (char *fs,...)
  439.  
  440.             A variable-argument version of bbslog.  It performs a
  441.             printf()-like processing of the format string, just as printf
  442.             does, and the result is sent to bbslog().
  443.  
  444.         int qprintf (char *fs,...)
  445.  
  446.             a printf() function that sends output to the current user, as
  447.             determined by the global variable 'who'.
  448.  
  449.         int qputs (char *fs)
  450.  
  451.             Sends a null-terminated text string to the current user, adding
  452.             a new line sequence at the end.  The current user is determined
  453.             by the value of the 'who' variable.
  454.  
  455.         int send (char *fs)
  456.  
  457.             Same as qputs, but does not add a newline sequence.
  458.  
  459.         int sendtoline (int w, char *fs)
  460.  
  461.             Same as send, but output goes to line 'w', instead of line 'who'.
  462.  
  463.         void broadcast (char *fs, int *linenums)
  464.  
  465.             Send the null-terminated text in 'fs' to the lines listed in the
  466.             array linenums.  linenums is an array containing all the lines
  467.             to receive the text, and a -1 after the last line.
  468.             For example :
  469.  
  470.             myfunc()
  471.             {
  472.               char *str="Hello World.";
  473.               int  dest[10];
  474.  
  475.               dest[0]=1;
  476.               dest[1]=3;
  477.               dest[2]=7;
  478.               dest[3]=-1;
  479.               broadcast(s,dest);
  480.             }
  481.  
  482.             This will send the text 'Hello World.' to lines 1, 3, and 7.
  483.  
  484.         void sendmsg (int line, int msgnum)
  485.  
  486.             Send the canned message identified by 'msgnum' to the speficied
  487.             line. The canned messages are in the files msgtext.msg,
  488.             msgansi.msg, and msgrip.msg.
  489.  
  490.         void getmsg (int ansiflag, int msgnum, char *fs)
  491.  
  492.             Get a copy of the message 'msgnum' into the character string
  493.             'fs'. If ansiflag is 0, get it from msgtext.msg.  If ansiflag is
  494.             1, get it from msgansi.msg.  If ansiflag is 2, get it from
  495.             msgrip.msg.
  496.  
  497.         int qgets (char *s, int len)
  498.  
  499.             Wait for a line of input from the current line, as specified by
  500.             'who'.  This function will return when the user hits ENTER.  The
  501.             string 's' will be filled with the null-terminated input,
  502.             without carriage return.  The return value is the length of the
  503.             returned string, which may be 0 if the user just pressed ENTER.
  504.  
  505.         int tqgets (char *s, int len, int timeout)
  506.  
  507.             Same as qgets, but waits 'timeout' seconds, and if no input is
  508.             received, returns a 0.
  509.  
  510.         void waitforempty (void)
  511.  
  512.             Wait until all pending output for the current line, as specified
  513.             by 'who', has been transmitted.
  514.  
  515.         int getserialnum (char *s)
  516.  
  517.             Gets the serial number of the Falken BBS and places it in s.
  518.  
  519.         int getversion (char *s)
  520.  
  521.             Gets the version number of the Falken BBS and places it in s.
  522.  
  523.         int btrv (int op, int file_id, void *addr, int *len,
  524.                   ENTRY * key, int keynum)
  525.  
  526.             Calls the Falken database interface function.  See the separate
  527.             section on Database Functions.
  528.  
  529.         void btupag (int lines)
  530.  
  531.             Sets the number of lines on the user's output screen.  If any
  532.             single block of text spans more than 'lines' screen lines, the
  533.             user will be sent the 'Press [ENTER] to continue message.'.  If
  534.             'lines' is 0, then paging does not occur.
  535.  
  536.         void setwatchdog (int value)
  537.  
  538.             Tell Falken BBS to monitor this line for carrier drop (value !=
  539.             0), or to stop monitoring for carrier drop (value = 0).
  540.  
  541.         void btuech (int echoval)
  542.  
  543.             Set the echo type for the current line (who).
  544.                 echoval = ECHO_ON   - normal echo
  545.                 echoval = ECHO_OFF  - no echo
  546.                 echoval = ECHO_DOTS - echo dots (used when password is
  547.                                       requested)
  548.  
  549.         int find_account (char *s1, void *mem)
  550.  
  551.             Loads the account database record for the user whose handle or
  552.             account number is in s1.  mem should be the address of an
  553.             acct_rec structure.
  554.  
  555.         int start_a_door (char *cmd)
  556.  
  557.             Calls load_a_door and wait_for_door.
  558.  
  559.         void wait_for_door (int pid)
  560.  
  561.             Wait until the child process 'pid' terminates.
  562.  
  563.         int load_a_door (char *cmd)
  564.  
  565.             Loads the task specified in the string 'cmd'.  The pid of the
  566.             new child process is returned.  -1 is returned on error.
  567.  
  568.             A possible side-effect of using this function is system lockup,
  569.             if the caller is careless.  The initialization event is sent to
  570.             the new task so that the init() process in the new child can
  571.             load the proper variables.  If the child task is not a Falken
  572.             door, then the caller must remove the initailization event
  573.             itself by calling recvmsg(), described in the CSWITCH
  574.             documentation.
  575.  
  576.         void save_acct (void)
  577.  
  578.             Save the account information for the user on line 'who'.  This
  579.             is useful if your door alters the account information.
  580.  
  581.         int sendtoq (char *fs, int qnum)
  582.  
  583.             Send the specified character string in fs, to a specific message
  584.             queue.  This can be used to send messages between one door and
  585.             another.
  586.  
  587.         int finduser(char *text, char **ret_text)
  588.  
  589.             This function is used to find the line number a particular user
  590.             is on.  This function is used in the '.send' commands and other
  591.             global commands where a user's name is used.  If the user is
  592.             online, the return value is the line number they are on.
  593.  
  594.             text is a pointer to a character string which contains a user
  595.             name followed (optionally) by some text.  ret_text should be the
  596.             address of a character pointer, which will be loaded with the
  597.             address of the first byte following the user name.  Example :
  598.  
  599.                 char *p;
  600.                 finduser("herb hello there",&p);
  601.  
  602.             This will return the line number that 'herb' is logged onto, and
  603.             will load 'p' with the address of the ' ' following 'herb'.
  604.  
  605.  
  606.     Database Functions
  607.     ------------------
  608.  
  609.         Falken uses a very efficient B+tree file manager and data manager
  610.         interface which is built into the mainbbs.exe program.  External
  611.         modules can take advantage of Falken's database capabilities via a
  612.         database API which is included in doorutil.c
  613.  
  614.         A brief review of Falken's database capabilities is in order.
  615.  
  616.         Falken's database is a single-key, B+Tree index associated with
  617.         variable-length records.  Each database consists of an index file,
  618.         which is managed by the B+Tree routines for fast access, and a data
  619.         file which is simply a flat file containing data records.  The index
  620.         file contains KEYS, which are null-terminated character strings, and
  621.         VALUES, which indicate the position of the data record in the flat
  622.         file.
  623.  
  624.         Neither the index file nor the flat file is directly editable. The
  625.         index file is a binary file, and contains a multi-level, balanced
  626.         B+Tree.  The data file may contain ordinary text, but each record is
  627.         preceeded by a 2-byte value containing the number of bytes in the
  628.         record.  This 2-byte value is a binary number, and cannot be edited.
  629.  
  630.         Functions which can be performed on the databases are :
  631.  
  632.           + open a database
  633.           + close a database
  634.           + create a database
  635.           + insert records into a database (key and data)
  636.           + insert only data records into a database
  637.           + search a database, looking for exact key matches
  638.           + search a database, looking for a key greater than or equal to
  639.             a key value
  640.           + search a database, looking for a key less than or equal to a
  641.             key value
  642.           + find the first record in a database
  643.           + find the last record in a database
  644.           + retrieve the next or previous record in a database
  645.           + update a database entry
  646.           + delete a database entry
  647.  
  648.         All functions are performed via a call to the btrv() function in
  649.         doorutil.c .   The specific function that you wish to perform is
  650.         included as a parameter.
  651.  
  652.         If there are no errors, btrv returns OK.  A negative value on the
  653.         return indicates an error.  If the b_open function is being used, a
  654.         non-negative return is the new database handle.
  655.  
  656.         The btrv function has the following prototype :
  657.  
  658.           int btrv(int function, int handle, void *data, int *size, ENTRY *key,
  659.                  int keynumber);
  660.  
  661.           data : the address of the first byte of data to write, or the
  662.                  address to place the data during read operations.
  663.                  Normally, this will be the address of a structure of the
  664.                  specified type, such as the address of an acct_rec
  665.                  structure or email_rec structure.
  666.  
  667.           size : this is the address of an integer.  For write operations,
  668.                  put the number of bytes to write, often written as
  669.                  sizeof(struct ...).  For read operations, the variable is
  670.                  loaded by the btrv function with the number of bytes that
  671.                  were read from disk.  This is why the address is passed,
  672.                  so that btrv() can fill it with the bytes read.
  673.  
  674.           key  : this is the address of an ENTRY variable.  ENTRY is a
  675.                  typedef for a structure of the type :
  676.                     typedef struct
  677.                     {
  678.                         unsigned long recptr;
  679.                         int reclen;
  680.                         char key[32];
  681.                     } ENTRY;
  682.                  recptr is the VALUE portion of the B+Tree database, and is
  683.                  normally the offset within the data file where the record
  684.                  for this key begins.  reclen is the number of bytes of this
  685.                  record.  key is a char array containing a null-terminated
  686.                  string which is the key for this record.
  687.  
  688.           keynumber : this field is only used by the b_create and b_open
  689.                  functions at present.  In these calls, a 0 in this field
  690.                  indicates that duplicate keys are not allowed.  A non-0
  691.                  indicates that duplicate keys are allowed.
  692.  
  693.                  Future modifications may include the ability to have
  694.                  multiple keys in a database.  In that case, this variable
  695.                  will be used to identify the key to search.  Always set it
  696.                  to 0 for read and write operations for now.
  697.  
  698.  
  699.           handle : the database identifier.  Falken has at least 6 databases
  700.                  open at all times.  User modules may open more.  The
  701.                  database handle identifies the database you wish to access
  702.                  with this call to btrv().  The database handle is returned
  703.                  by the btrv() function when b_open or b_create is the
  704.                  function.
  705.  
  706.                  There are 6 special database handles - the handles for the
  707.                  6 databases that Falken has permanently opened.  These are :
  708.                     ACCT_FILE       - user accounts, indexed by handle
  709.                     DLOAD_FILE      - download files, indexed by lib & name
  710.                     EMAIL_FILE      - email file, indexed by handle
  711.                     MSG_FILE        - message base file, indexed by folder
  712.                     BIOS_FILE       - user bio file, indexed by handle
  713.                     ACCT_NUM_FILE   - account numbers file, indexed by acct #
  714.                  These databases should not be opened or closed by the door
  715.                  module - simply assume that they are open and available,
  716.                  and include the above constant as the handle for the btrv
  717.                  call.
  718.  
  719.           function : one of the following constants :
  720.             b_open - open an existing database.  Use (-1) as the handle, and
  721.                  provide the name of the index file as the data pointer and
  722.                  the name of the data file as the key pointer.  The
  723.                  'keynumber' parameter should be 0 to indicate that
  724.                  duplicate keys are not allowed, and 1 to indicate that
  725.                  duplicate keys are allowed.  Example :
  726.  
  727.                      myhandle=btrv(b_open,-1,"mydb.idx",&size,"mydb.dat",1);
  728.                      size=1000;
  729.                      btrv(b_getlow,myhandle,dataptr,&size,&mykey,0);
  730.  
  731.                  This example opens a database called 'mydb', with duplicate
  732.                  keys allowed.  It then uses the handle returned from the
  733.                  b_open to read the first record in the database.
  734.  
  735.             b_create - create a new database.  This call is exactly like
  736.                  b_open, except that it will create new idx and data files,
  737.                  and destroy those files if the already exist.
  738.  
  739.             b_close - the only parameter to this call that is used is the
  740.                  handle.  It closes the specified database.  Example :
  741.  
  742.                     btrv(b_close, myhandle, NULL, NULL, NULL, 0);
  743.  
  744.             b_insert - insert a new record into the database.  The key
  745.                  parameter contains a pointer to the ENTRY structure which
  746.                  has the key value to store in the index file.  The data
  747.                  pointer is a pointer to the data.  The size parameter is
  748.                  the address of an integer containing the number of bytes to
  749.                  write to the data file.  Example :
  750.  
  751.                     int size;
  752.                     ENTRY mykey;
  753.                     char somedata[100];
  754.  
  755.                     strcpy(mykey.key,"key1");
  756.                     size=100;
  757.                     btrv(b_insert,myhandle,somedata,&size,&mykey,0);
  758.  
  759.                  This writes 100 bytes of data into the database identified
  760.                  by 'myhandle'.  This data can be retrieved by performing
  761.                  the 'b_getequ' function with the value "key1" in mykey.key.
  762.  
  763.             b_getequ - find an exact database entry, based on the key.
  764.  
  765.                     int size;
  766.                     ENTRY mykey;
  767.                     char somedata[100];
  768.  
  769.                     strcpy(mykey.key,"key1");
  770.                     size=100;
  771.                     btrv(b_getequ,myhandle,somedata,&size,&mykey,0);
  772.  
  773.                  In this case, the size variable is loaded with the MAXIMUM
  774.                  number of bytes to read, and the mykey.key variable is
  775.                  loaded with the key to find.  If there are multiple
  776.                  instances of this key, it will return the first one in the
  777.                  file. The rest can be retrieved with the b_getnext
  778.                  function.
  779.  
  780.                  If no entries exist for the specified key, an error is
  781.                  returned.  Otherwise, the return is OK.
  782.  
  783.             b_getgt - find the first key whose value is > the specified key.
  784.             b_getlt - find the first key whose value is < the specified key.
  785.             b_getge - find the first key whose value is >= the specified key.
  786.             b_getle - find the first key whose value is <= the specified key.
  787.             b_getlow - find the first key (lowest value) in the database
  788.             b_getgt - find the last key (highest value) in the database
  789.             b_getnext - find the next key in the database.
  790.             b_getprev - find the previous key in the database.
  791.  
  792.                  For the b_getnext and b_getprev functions to work
  793.                  correctly, there must be a preceeding b_get* call
  794.                  (b_getequ, b_getlow, etc) to position the database.  The
  795.                  values in the ENTRY variable are used by Falken to traverse
  796.                  the index file, so they must not be altered between the
  797.                  calls.
  798.  
  799.                  Some examples ...
  800.  
  801.                  To read ALL entries in a file..
  802.  
  803.                     int status;
  804.                     int size;
  805.                     ENTRY mykey;
  806.                     char somedata[100];
  807.  
  808.                     size=100;
  809.                     status=btrv(b_getlow,myhandle,somedata,&size,&mykey,0);
  810.                     while(status==OK)
  811.                     {
  812.                        .. do some work ..
  813.                        size=100;
  814.                        status=btrv(b_getnext,myhandle,somedata,&size,&mykey,0);
  815.                     }
  816.  
  817.                  To read ALL entries in a file for the key 'Herb' :
  818.  
  819.                     int status;
  820.                     int size;
  821.                     ENTRY mykey;
  822.                     char somedata[100];
  823.  
  824.                     size=100;
  825.                     strcpy(mykey.key,"Herb");
  826.                     status=btrv(b_getequ,myhandle,somedata,&size,&mykey,0);
  827.                     while((status==OK) && (strcmpi(mykey.key,"Herb")==0))
  828.                     {
  829.                        .. do some work ..
  830.                        size=100;
  831.                        status=btrv(b_getnext,myhandle,somedata,&size,&mykey,0);
  832.                     }
  833.  
  834.                Note that the b_getnext and b_getprev calls use the ENTRY
  835.                variable without loading any new values.  All functions
  836.                that retrieve data from the database, such as b_getequ
  837.                b_getnext b_getlow etc, store the current key value, key
  838.                location, and data pointer, into variables within the
  839.                ENTRY structure.  These values are then used to
  840.                accurately position the database for the next call.
  841.  
  842.             b_getdirect - this is a variation of the b_getequ function. It
  843.                finds the database record that matches both the key and the
  844.                recptr value that you load into the ENTRY structure.  If
  845.                there are multiple records with the same key, (several emails
  846.                for the same user, for example) then b_getequ will always
  847.                return the first record that matches the key, in effect
  848.                always returning the first record in the set of records that
  849.                has the same key.  If you wish to access a SPECIFIC record
  850.                within that set, without using the b_getnext loop, and if you
  851.                have previously saved the recptr value from a btrv() call for
  852.                that record, you can read it directly.  Such as :
  853.  
  854.                     size=100;
  855.                     strcpy(mykey.key,"Herb");
  856.                     mykey.recptr = oldrecptrvalue;
  857.                     status=btrv(b_getdirect,myhandle,somedata,&size,&mykey,0);
  858.  
  859.             b_update - modify an existing record.  Example :
  860.                     int status;
  861.                     int size;
  862.                     ENTRY mykey;
  863.                     char somedata[100];
  864.  
  865.                     size=100;
  866.                     strcpy(mykey.key,"Herb");
  867.                     status=btrv(b_getequ,myhandle,somedata,&size,&mykey,0);
  868.                     if(status==OK)
  869.                     {
  870.                         somedata[0]='Q';  /* make a change.... */
  871.                  /*
  872.                  ** leave the mykey structure alone, and the size
  873.                  ** value alone, since they were set by b_getequ
  874.                  */
  875.                         status=btrv(b_update,myhandle,somedata,&size,&mykey,0);
  876.                         if(status < 0)
  877.                         {
  878.                             qprintf("Error %d in b_update.\r", status);
  879.                         }
  880.                     }
  881.                     else
  882.                     {
  883.                         qprintf("Error %d in b_getequ.\r", status);
  884.                     }
  885.  
  886.             b_append - this function is used to write only the data portion
  887.                of the database.  No key is written to the index file.  This
  888.                may sound like it is of little use, but it has it's uses. For
  889.                example : the message base database contains records which
  890.                are simply arrays of pointers to the message base text.  The
  891.                actual message base text is stored using the b_append
  892.                function, and the keyed records contain the pointers to these
  893.                text records.  This takes a little extra effort on the part
  894.                of the programmer, but can be very efficient.
  895.  
  896.                The offset at which the data was written is loaded into the
  897.                recptr member of the ENTRY structure by btrv().
  898.  
  899.             b_getdata - this function is used to read data directly from the
  900.                data file.  The recptr member of the ENTRY structure is
  901.                loaded with the position in the file to start reading, then
  902.                the btrv() function is called.  The data file is read
  903.                directly at the position specified.
  904.  
  905.                This is logically the opposite of b_append, and normally, the
  906.                value returned in the recptr member of ENTRY by the b_append
  907.                call will be used as the recptr position in this b_getdata
  908.                call.
  909.  
  910.  
  911.         Message Base File Format.
  912.  
  913.           The Message Base file (MSGBASE.IDX, MSGBASE.DAT) contains both
  914.           control records and data records.  The control records are in the
  915.           format 'msg_record' in structs.h.  Basically, it works like this :
  916.             Each message has a 'msg_record' structure in the database.
  917.             The msg_record structure contains the message number, time stamp
  918.                 of the last update, subject, and NUMBER OF RESPONSES.
  919.             The msg_record also contains a 'resp' structure for each response
  920.                 to the message (the original message is resp[0]).  The
  921.                 'resp' structure contains the user name that wrote the
  922.                 response, time stamp of the response, and an unsigned long
  923.                 called msgsequence.
  924.             MSGSEQUENCE is the offset within the data file to read the text.
  925.             As with all database entries, there is an integer containing the
  926.             size of the text, followed by the text itself.
  927.  
  928.           To read the message base records - read the control record
  929.           (msg_record) first, then for each response (respcnt), read the
  930.           text pointed to by the msgsequence member of the resp structure
  931.           for that response.
  932.  
  933.           Use the 'b_getdata' function when calling BTRV() to read from a
  934.           specific location in the data file.
  935.  
  936.           Nothing to it.
  937.  
  938.           A note : when searching for NEW posts, Falken first examines the
  939.           'last_update' field in msg_record, and if that time stamp is newer
  940.           than the users time stamp in that folder, the message is selected
  941.           for reading.  Then, each response is checked for a time stamp
  942.           later than the users time stamp.  Text is only read when the time
  943.           stamp for that response is later than the users time stamp.
  944.  
  945.  
  946. Information about the USERBIO and Email List structures
  947.  
  948.  /*
  949.  ** Questionaire processor for Falken.
  950.  ** The sysop can change the questions around, by changing the questions in
  951.  ** messages.msg numbered MC_USERINFO_Q1 - MC_USERINFO_Q5
  952.  ** If less than 5 questions are asked, then the end of the questions list
  953.  ** is marked with the message '$END'
  954.  **
  955.  ** The display of the answers is governed by the message MC_USERINFO_FORMAT
  956.  ** This is a normal text display, with fields filled in with the answers
  957.  ** provided by users.  Text to be filled with answers is in this format
  958.  **    %2                %
  959.  ** where everything between the % signs is filled in with answer #2.
  960.  **
  961.  ** The user is then given up to 15 lines of freeform text to say something
  962.  ** about themselves.
  963.  */
  964.  
  965. struct bio_record
  966. {
  967.     char username[uidlen];
  968.     char ans[5][60];
  969.     char text[15 * 80];
  970. };
  971.  
  972. The mail list record is stored in the user bio database,
  973. using the owner's name as the key, with a '@' in front...
  974. such as '@herb'
  975.  
  976. struct mail_list_rec
  977. {
  978.         char owner_id[uidlen];
  979.         char listname[20];
  980.         int entrycount;
  981.         char list_entry[64][32];
  982. };
  983.  
  984.  
  985.  
  986.  
  987.         Falken uses the C/Database Toolchest from MIX software to provide
  988.         the B+Tree functionality.  I have written the btrv() function which
  989.         acts as the Falken API to the database functions.  If you wish to
  990.         write offline utilities, such as utilities to clean up or compact
  991.         database files, I will provide the btrv() object file, to be linked
  992.         with the C/Database Toolchest library, for a stand-alone DOS
  993.         executable for database access.
  994.  
  995.         MIX provides excellent quality programming tools at incredibly low
  996.         prices.  I highly recommend them.
  997.  
  998.  
  999.     Introduction to Falken Serial I/O capabilities.
  1000.     -----------------------------------------------
  1001.  
  1002.         Falken's serial I/O capabilities are provided by an internal
  1003.         interrupt handler.  It is not necessary (or smart) to load an
  1004.         external program to control the serial ports.  Falken uses an
  1005.         'interrupt-driven poll' method to control the serial ports.  In
  1006.         short, this is what happens :
  1007.  
  1008.         Falken examines the hardware configuration, and determines the
  1009.         highest baud rate used by the system.  It then calculates how many
  1010.         times per second it must test the serial hardware to see if any of
  1011.         the modems need servicing.  The timer chip in the PC is then
  1012.         reprogrammed to interrupt Falken at that interval, and Falken
  1013.         queries the appropriate serial ports, looking for ports that require
  1014.         servicing (character received, transmit ready, DCD change, etc.).
  1015.  
  1016.         This method overcomes a very serious shortcoming in PC hardware
  1017.         design - there are only 2 IRQ vectors assigned to serial ports. If
  1018.         more than 2 serial devices are required, they must share these IRQ
  1019.         vectors.  If the devices that are sharing a vector are on different
  1020.         cards (2 or more internal modems, for instance), then interrupts can
  1021.         be lost.  So Falken does not use the normal serial I/O interrupts.
  1022.         In fact, the IRQ setting of the serial device is irrelevant, and not
  1023.         used by Falken at all.
  1024.  
  1025.         Falken's serial driver is Fossil compatible.  This means that the
  1026.         driver conforms to most of the Fossil specification found in the
  1027.         FOSSIL.DOC file included with this archive.  It supports a 'text'
  1028.         mode also.
  1029.  
  1030.         In 'text' mode, Falken will :
  1031.             Echo incoming characters (keystrokes) back out to the port.
  1032.  
  1033.             Filter input according to an internal translation table.
  1034.  
  1035.             Characters to be echoed back to the port are held until the
  1036.             current output has completed.  If you start typing in the middle
  1037.             of a menu, for example, your keystrokes will not be echoed until
  1038.             the current transmission is completed.
  1039.  
  1040.             On output, Line Feed characters are ignored, but a line feed is
  1041.             automatically transmitted after every carriage return, if the
  1042.             user configuration specifies to do so.
  1043.  
  1044.             Backspace is echoed as '<BS><SPACE><BS>', so that a BS is
  1045.             destructive, but a BS will only be echoed if there are
  1046.             characters in the input buffer.
  1047.  
  1048.             Characters in the output buffer are not transmitted while there
  1049.             are characters in the input buffer.  This prevents the user's
  1050.             input from being intermixed with output text.
  1051.  
  1052.         The net effect is a more fluid user interface.  Once the user's
  1053.         input starts echoing out, it will not be interrupted by output from
  1054.         Falken. Aborted output will not terminate in the middle of a line of
  1055.         text. Output from Falken will not be interrupted by echoed
  1056.         keystrokes, until the current 'record' is completed.
  1057.  
  1058.         A door program that receives input from Falken via message
  1059.         queues does not have to worry about echoing characters or cr/lf
  1060.         conversions, etc. The line will still be in 'text' mode, since
  1061.         Falken is still in control of the port.  This is not true if the
  1062.         door uses the port directly.
  1063.  
  1064.         If a door program requests direct control of the serial port,
  1065.         the port is placed into 'raw' mode.  All 'text' mode controls
  1066.         are disabled, and the program is given a raw serial port which
  1067.         responds to Fossil calls.
  1068.  
  1069.         To take direct control of the serial port, a door *MUST* inform
  1070.         Falken that it wishes to do so.  Otherwise, both Falken and the
  1071.         door program will be trying to obtain input and send output to
  1072.         the same port, and very likely nothing will work right.  To gain
  1073.         control of the serial port, call the setbinarymode() function in
  1074.         doorutil.
  1075.  
  1076.         As soon as Falken receives the binary mode request, it places
  1077.         the port into binary mode (i.e. takes it out of text mode), and
  1078.         relinquishes all control of the port.  When the door stops,
  1079.         Falken takes the port back. The only real checking that Falken
  1080.         does while the door has control of the port, is to check for
  1081.         Carrier Detect.  If DCD drops, Falken will clean up the port and
  1082.         terminate the door.
  1083.  
  1084.         See the file transfer protocols for examples of taking control
  1085.         of the serial port.
  1086.  
  1087.         Falken's built-in FOSSIL support provides the following standard
  1088.         FOSSIL services to doors that have control of the serial port :
  1089.  
  1090.         Function 0  : Set Baud Rate (will not work if port is FIXED BAUD)
  1091.         Function 1  : Transmit Character with wait
  1092.         Function 2  : Receive Character with wait
  1093.         Function 3  : Status Request
  1094.         Function 4  : Initialize Port
  1095.         Function 5  : De-initialize Port
  1096.         Function 6  : Raise/Lower DTR
  1097.         Function 7  : Return System Timer Parameters
  1098.         Function 8  : Flush Output Buffer
  1099.         Function 9  : Purge Output Buffer
  1100.         Function A  : Purge Input Buffer
  1101.         Function B  : Transmit Character, no wait
  1102.         Function C  : Non-destructive 'peek' at input buffer
  1103.         Function F  : Flow Control
  1104.         Function 18 : Read Block
  1105.         Function 19 : Write Block
  1106.         Function 1A : Start/Stop Break Signal
  1107.         Function 1B : Get Driver Information
  1108.  
  1109.         Functions 1, 2, and 8 specify that the Fossil driver will not
  1110.         return to the caller until the condition is satisfied.  In this
  1111.         case, the Fossil driver will use the Cswitch RELINQ() call to
  1112.         allow other tasks to run until the condition is met.
  1113.         Performance is not affected by using these calls.
  1114.  
  1115.     ;
  1116.     ; Description of functions in FSUBS.ASM
  1117.     ;
  1118.     ; This is aset of C-linkable routines, in large model, for directly
  1119.     ; calling a Fossil driver.
  1120.     ;
  1121.  
  1122.     function name :  f_recv
  1123.     Callinq sequence :  f_recv(int line, char *buffer, int maxcount)
  1124.     Description :
  1125.         Reads up to 'maxcount' bytes from the specified line.  The bytes
  1126.         are placed into 'buffer'.  If fewer than 'maxcount' bytes are
  1127.         available in the input buffer, they are all retrieved.
  1128.  
  1129.         Returns the number of characters placed into buffer.
  1130.  
  1131.  
  1132.     function name :  f_send
  1133.     Callinq sequence :  f_send(int line, char *buffer, int count)
  1134.     Description :
  1135.         Sens 'count' bytes to the specified line for transmission.  Returns
  1136.         immediately, without waiting for characters to be transmitted.
  1137.         Returns the number of characters placed into the Fossil output buffer
  1138.         for transmission.  If there is not enough room for all the characters
  1139.         to fit, the returned value will be less than 'count'.
  1140.  
  1141.  
  1142.     function name :  f_status
  1143.     Callinq sequence :  f_status(int line)
  1144.     Description :
  1145.         Returns the current status of the specified line.
  1146.         See FOSSIL.DOC for a description of the status byte.
  1147.  
  1148.  
  1149.     function name :  f_setbaud
  1150.     Callinq sequence :  f_setbaud(int line, int param)
  1151.     Description :
  1152.         Uses FOSSIL function 0 to set line parameters to 'param'.
  1153.         See FOSSIL.DOC for more details.
  1154.         Usually, a door task will not need to call this function.
  1155.  
  1156.  
  1157.     function name :  f_initialize
  1158.     Callinq sequence :  f_initialize(int line)
  1159.     Description :
  1160.         Uses FOSSIL service 4 to initialize the port.
  1161.         Usually, a door task will not need to call this function.
  1162.  
  1163.  
  1164.     function name :  f_dtr
  1165.     Callinq sequence :  f_dtr(int line, int onoff)
  1166.     Description :
  1167.         Sets the DTR (Data Terminal Ready) on or off for the specified port,
  1168.         according to whether 'onoff' is zero or non-zero.  A zero value
  1169.         turns DTR off.
  1170.         A door program usually will not need to call this function.
  1171.  
  1172.  
  1173.     function name :  f_flushin
  1174.     Callinq sequence :  f_flushin(int line)
  1175.     Description :
  1176.         Uses FOSSIL service 0A to flush the ports input buffer.
  1177.         All characters in the input buffer are discarded immediately.
  1178.  
  1179.     function name :  f_flushout
  1180.     Callinq sequence :  f_flushout(int line)
  1181.     Description :
  1182.         Uses FOSSIL service 9 to flush the ports output buffer.
  1183.         All characters in the output buffer are discarded immediately, and
  1184.         are not transmitted.
  1185.  
  1186.  
  1187.     function name :  f_flowctrl
  1188.     Callinq sequence :  f_flowctrl(int line, int flow)
  1189.     Description :
  1190.         Uses FOSSIL service 0F to set flow control parameters for the line.
  1191.         See FOSSIL.DOC for a description of 'flow'.
  1192.  
  1193.  
  1194.     function name :  f_purgeout
  1195.     Callinq sequence :  f_purgeout(int line)
  1196.     Description :
  1197.         Uses FOSSIL service 8 to wait until the output buffer is empty.
  1198.  
  1199.  
  1200.     function name :  f_writechar
  1201.     Callinq sequence :  f_writechar(int line, int data)
  1202.     Description :
  1203.         Uses FOSSIL service 1 to write 'data' out to the line.  If there is
  1204.         not room in the output buffer for one character, this service waits
  1205.         until room is available.
  1206.  
  1207.  
  1208.     function name :  f_readchar
  1209.     Callinq sequence :  f_readchar(int line)
  1210.     Description :
  1211.         Uses FOSSIL service 2 to read a character from the input line.  If
  1212.         no characters are available, it waits until a character is received.
  1213.         Returns the next character from the line's input buffer.
  1214.  
  1215.  
  1216.     function name :  f_getibw
  1217.     Callinq sequence :  f_getibw(int line)
  1218.     Description :
  1219.         Returns the number of characters waiting in the line's input buffer.
  1220.  
  1221.     function name :  f_getoba
  1222.     Callinq sequence :  f_getoba(int line)
  1223.     Description :
  1224.         Returns the number of characters that can be written to the file's
  1225.         output buffer.
  1226.  
  1227.     function name :  f_insertchar
  1228.     Callinq sequence :  f_insertchar(int line, int chr)
  1229.     Description :
  1230.         Inserts the single character 'chr' into the input buffer for the
  1231.         specified line.
  1232.  
  1233.  
  1234.     BINARY MODE Considerations.
  1235.     ---------------------------
  1236.  
  1237.         The Falken low-level communications routines do a lot of work
  1238.         while in text mode.  Word-wrapping of output, translation of CR
  1239.         to CR/LF if the user requires it, masking of control codes on
  1240.         input, that kind of thing. In Binary mode, Falken does not do
  1241.         anything to the data going in or out!  This means that there
  1242.         will be no word-wrap, no echo, no CR to CRLF translation, no
  1243.         filtering of input, in other words, no text handling of any
  1244.         kind.  Your task will have to make FOSSIL calls to determine the
  1245.         status of the line, read input data, and send output to the
  1246.         line. Falken will monitor Carrier Detect (DCD) from the modem,
  1247.         and will hang up the line and terminate the door if DCD drops.
  1248.  
  1249.         If you are writing a program that will work off 'hot keys', that
  1250.         is, one that requires only a particular key to be pressed,
  1251.         without hitting ENTER, then you will need to use binary mode.
  1252.         Also, if you wish to write a new file transfer protocol for
  1253.         Falken, you will have to use Binary mode.
  1254.  
  1255.         For most programs, however, leaving the line in text mode is the
  1256.         easiest, best way to go.  Since Falken will handle the
  1257.         nitty-gritty
  1258.  
  1259.  
  1260.  
  1261.     PORTING DOORS TO FALKEN.
  1262.     ------------------------
  1263.  
  1264.         It has been my experience that doors written for other BBS
  1265.         programs do not work well with Falken.  Most doors written for
  1266.         other BBSes assume that they have complete control of the
  1267.         hardware, and insert their own interrupt handlers and such.
  1268.         Other programs assume they are running under one specific BBS
  1269.         program, and perform functions unique to it, such as Wildcat's
  1270.         logging functions.
  1271.  
  1272.         To port a door to Falken, make sure it uses Fossil-style calls
  1273.         to the serial port (INT 14H).  Also, make sure that code is
  1274.         written to read the initialization event from Falken and use the
  1275.         information it contains.
  1276.  
  1277.         Some doors require a text file which contains the information
  1278.         needed by the door.  CALLCNT.BBS and WWIVs CHAIN files are
  1279.         examples.  It is possible to use these doors directly, with a
  1280.         little work.  You need to write a 'loader' program which will :
  1281.  
  1282.         1. Read Falkens initialization message, and pull the information
  1283.            required by the door from the various structures this gives
  1284.            you access to. This is done with the init() function.
  1285.  
  1286.         2. Get direct control of the serial port (setbinarymode())
  1287.  
  1288.         3. Write the text file in the correct format for the door.
  1289.  
  1290.         4. Use the EXECTASK() call to replace your 'loader' task with
  1291.            the real door.  The door will inherit your tasks files and
  1292.            Falken will not even realize that another door has taken the
  1293.            place of the one it started.  At this point, the real door
  1294.            can make Fossil calls to control the port, and everything
  1295.            else it needs to do.
  1296.  
  1297.  
  1298.     RUNNING DOS TASKS.
  1299.     ------------------
  1300.  
  1301.         Falken also supports I/O redirection for STDIN/STDOUT.  If you
  1302.         have a program that runs under DOS, and does not write directly
  1303.         to screen memory, and does not read keys directly from the
  1304.         keyboard, then Falken can run it as a door, and redirect input
  1305.         and output to the serial port.  To load such a program, put the
  1306.         word 'dos' in front of the program name in the menu.
  1307.         Like this :   'dos myprog.exe'
  1308.  
  1309.         In general, if you program uses the printf(), puts(), putchar(),
  1310.         scanf(), gets(), getchar() type functions for I/O, this will
  1311.         work.  In fact, I have successfully run COMMAND.COM as a door
  1312.         under Falken.  This is not recommended, but it is possible.
  1313.  
  1314.  
  1315.     WRITING MULTI-PLAYER GAMES.
  1316.     ---------------------------
  1317.  
  1318.         There are 2 possible approaches to writing multi-player games.
  1319.  
  1320.         The first approach is to run a copy of the game for each player,
  1321.         and communicate between the various tasks using message queues.
  1322.         The other method is to run a single copy of the game, and have
  1323.         it perform all communication for all players.
  1324.  
  1325.         The first method will work pretty well.  The user_rec structure
  1326.         for each line contains the queue numbers to be used for I/O with
  1327.         that user. It is a simple matter to format an information
  1328.         message concerning your players moves, and send it to the
  1329.         program being run for the other players.  The Connect-4 game
  1330.         works this way.
  1331.  
  1332.         --------------       _________
  1333.         |            | ==== | Door 1  |       Only door 1 communicates with
  1334.         |  Falken    |      |_________|       user 1
  1335.         |            |        |    ^
  1336.         |  BBS       |        |    |          Information passes between doors
  1337.         |            |       _v____|___
  1338.         |  Program   | ==== | Door 2   |      Door 2 communicates with user 2
  1339.         |            |      |__________|
  1340.         --------------
  1341.  
  1342.         The other method requires some explanation.
  1343.  
  1344.         Falken monitors the status of the door program that is loaded
  1345.         for each line.  If the door program terminates for any reason,
  1346.         Falken returns the user to the menu or area he was in prior to
  1347.         activation of the door. Also, if the user drops carrier or exits
  1348.         back to the BBS, Falken will terminate the door as quickly as it
  1349.         can, if the door continues to try to run.
  1350.  
  1351.         Obviously, it is not feasible for multiple users to be serviced
  1352.         by a single program.  There are times, however, when a single
  1353.         program handling multiple users is the ONLY way to go!  A
  1354.         solution ....
  1355.  
  1356.         Write a small door to be loaded and run when the user wants to
  1357.         enter the game.  This small door will examine the user_rec
  1358.         array, and determine if any other users are already running the
  1359.         door.  If not, it will load the program using the 'LOADTASK()'
  1360.         call.  The initialization event will be sent to the program
  1361.         using queue #1, just as if it were sent by Falken.
  1362.  
  1363.         The small door will now perform a 'SUSPEND()' call.  This leaves
  1364.         the task active, but removes it from execution.  It will stay
  1365.         dormant until another program issues a 'WAKEUP()' call for it.
  1366.         This satisfies Falkens check of program status for the user.
  1367.  
  1368.         The *REAL* game will loop, examining the the user_rec array,
  1369.         looking for players that are currently in the game.  This is
  1370.         done by examining the 'u_stat' variable (6 means the user is
  1371.         running an external program), and the 'doors_id' array (a char
  1372.         array with the name of the door being run). For each user that
  1373.         is currently in the game, check for data on that user's input
  1374.         queue.  Send outgoing text to that players output queue.
  1375.  
  1376.         --------------     __________
  1377.         |            |    | Door 1   | When real door is loaded, SUSPEND()
  1378.         |  Falken         | DORMANT  |
  1379.         |            |     ----------      ______________________________
  1380.         |  BBS       |====================| Real door communicates with  |
  1381.         |            |====================| all players currently in game|
  1382.         |            |     __________      ------------------------------
  1383.         |  Program   |    | Door 2   | When real door is loaded, SUSPEND()
  1384.         |            |    | DORMANT  |
  1385.         --------------     ----------
  1386.  
  1387.  
  1388.  
  1389.         When a player quits the game, or when the player's u_stat
  1390.         becomes != 6, issue a wakeup() for that users dormant task.  The
  1391.         TCB number is in the user_rec structure (u_doortcb).  i.e.
  1392.         wakeup((*user)[who].u_doortcb); The dormant task should exit
  1393.         immediately after the SUSPEND() call. i.e.
  1394.  
  1395.         HOG();
  1396.         copy a unique string into (*user)[who].doors_id to show we are
  1397.             playing the game. real program will look for doors_id with
  1398.             this 'signature';
  1399.         If (real program not loaded)
  1400.         {
  1401.             LOADTASK();
  1402.             send a copy of initialization event on queue #1;
  1403.         }
  1404.         NOHOG();
  1405.         SUSPEND();
  1406.         exit(); /* or endtask(), if you have an endtask() function */
  1407.  
  1408.         /* note - upper case names are for clarity only */
  1409.         /* use lower case in your program */
  1410.  
  1411.         The HOG() function is used to temporarily prevent other tasks
  1412.         from running.  You should do this when checking if the real
  1413.         program is loaded, to prevent 2 doors from trying to load the
  1414.         real program simultaneously.  The NOHOG() will allow
  1415.         multitasking to proceed normally.
  1416.  
  1417.         I have used this method to write multi-user programs.  I try to
  1418.         keep my programs from wasting processor time, so I program my
  1419.         'idle loop', to scan each users input queue one time, then I
  1420.         suspend execution for a while with either a sleep() or relinq()
  1421.         call.  I do not have an infinite loop running without
  1422.         relinquishing control of the processor in some way.
  1423.  
  1424.         When the last user has left the game, the *REAL* program simply
  1425.         exits.
  1426.  
  1427.         Examine the programs LOADTLCF.C and TLCFDEMO.C for a real
  1428.         example of using this method for implementing a multi-player
  1429.         program.
  1430.  
  1431.     Tricks that DOORS Play with Memory.
  1432.     -----------------------------------
  1433.  
  1434.         This section is important if you wish to write SMALL MODEL
  1435.         programs for Falken.  If you use LARGE model programs,
  1436.         everything will work just fine with no extra effort on your
  1437.         part.
  1438.  
  1439.         First, it is important to understand the difference between
  1440.         memory models on the 80x86 processors.  Since registers on the
  1441.         8086/8088 processors are only 16 bits wide, including the
  1442.         Instruction Pointer register, there is an address limitation of
  1443.         64 Kbytes (2^16).  To overcome this limitation, Intel uses 4
  1444.         special registers called SEGMENT registers to augment
  1445.         addressing.  Each segment register defines a BASE ADDRESS, with
  1446.         a resolution of 16 bytes. Each register, including the
  1447.         INSTRUCTION POINTER, provides an offset from that base address,
  1448.         from which the instruction or data is retrieved.  In actual
  1449.         practice, the segment is loaded as bits 4-19 of the 20-bit
  1450.         address, with bits 0-3 set to 0, then the offset is added to it,
  1451.         like this :
  1452.  
  1453.         Segment register  x x x x x x x x x x x x x x x x 0 0 0 0
  1454.         Offset                    x x x x x x x x x x x x x x x x +
  1455.                           ---------------------------------------
  1456.         Actual address    x x x x x x x x x x x x x x x x x x x x
  1457.  
  1458.         There are 4 segment registers.  They are :
  1459.  
  1460.           CS - Code segment register.  Normally combined with the IP to
  1461.                fetch the next executable instruction.
  1462.  
  1463.           DS - Data segment.
  1464.                Normally used to access memory.  Most instructions that
  1465.                manipulate memory in any way use the DS register by
  1466.                default.
  1467.  
  1468.           SS - Stack segment. All operations concerning the Stack
  1469.                Pointer and Base Pointer use SS by default.
  1470.  
  1471.           ES - Extra Segment.  This register is useful as an extra
  1472.                segment register. It is often used in conjunction with
  1473.                the DS register when moving data between segments.
  1474.  
  1475.         Since the segment register identifies base addresses on a
  1476.         16-byte (paragraph) boundary, and there are 64K possible values
  1477.         for the segment registers, we may address up to 1024K (64K * 16)
  1478.         or 1 Megabyte with the 8086/8088 processor, as well as a
  1479.         80286/80386/80486 operating in REAL mode.
  1480.  
  1481.         Why do we need to know this low-level esoterica?  Because
  1482.         compilers and assemblers for the 80x86 family of processors use
  1483.         these segment registers differently, based on the Memory Model
  1484.         chosen for the program.
  1485.  
  1486.         A SMALL memory model program will allow 64K data and stack, plus
  1487.         64K code. It sets the CS, DS, and SS registers to point to the
  1488.         beginning of each of these memory regions, and does not change
  1489.         them while the program is running.  Memory accesses are
  1490.         performed using 16-bit arithmetic for the addressing.  Only the
  1491.         IP is pushed onto the stack for a subroutine call, and all
  1492.         pointers require only a 16-bit offset to identify the datum to
  1493.         which they refer.
  1494.  
  1495.         A LARGE memory model program may have both code and data
  1496.         segments larger than 64K.  In this case,  memory accesses must
  1497.         specifically load the segment registers with the paragraph
  1498.         address of the datum to be accessed, since the datum may be in a
  1499.         different 64K segment.  Pointers must contain both the 16-bit
  1500.         offset, and the 16-bit base address (segment) to identify their
  1501.         datum. Subroutine calls must push both the IP and the CS onto
  1502.         the stack, and the CS and IP are both replaced by the 32-bit
  1503.         address of the target.
  1504.  
  1505.         Medium and Compact memory models are variants of these basic
  1506.         types, allowing either code or data segments to be larger than
  1507.         64K.  We will not concern ourselves with these models.
  1508.  
  1509.         The above discussion is important for Falken Door authors,
  1510.         because Falken passes some information to doors as POINTERS.  To
  1511.         be specific, Falken passes Large Model pointers (called FAR
  1512.         pointers) which contain both the 16-bit offset of data and the
  1513.         16-bit segment address of the data.  If you write programs in
  1514.         the SMALL memory model, there may be problems because of the
  1515.         different pointer sizes.
  1516.  
  1517.         An example is the copying of the users name to a local string.
  1518.         Falken passes the address of an array of structures, each of
  1519.         these structures contains one account record for the configured
  1520.         ports.  Falken also tells you which port YOUR user is on.  I use
  1521.         the variable 'who' to identify the port I am running for, and
  1522.         the pointer 'acct' to point to the address of account record
  1523.         structures.  To copy the users name into a local string called
  1524.         'name', you might code a line like this :
  1525.  
  1526.             strcpy(name, acct[who].acctname);
  1527.  
  1528.         The example instruction above should work normally.  If you have
  1529.         written your program in SMALL model, though, it will not.  The
  1530.         problem is that the strcpy function in the small model library
  1531.         cannot accept a far address for one of it's arguments.  The
  1532.         instruction will appear to work, but in fact will only use the
  1533.         offset of the account records structure, and index from YOUR DS
  1534.         register.  The result will be corruption of your program, or
  1535.         possibly, of someone elses program.  System lockups will surely
  1536.         result.
  1537.  
  1538.         If you built your program with LARGE model, there would be no
  1539.         problem, since the strcpy function in the large model library
  1540.         will accept 32-bit addresses for both arguments.
  1541.  
  1542.         So, how do you copy the account name if you have a small model
  1543.         program? The best way is to write your own copy routine.  Like
  1544.         this :
  1545.  
  1546.         far_strcpy(char far *dest, char far *src)
  1547.         {
  1548.             while(*dest++ = *src++);
  1549.         }
  1550.  
  1551.         When you call the far_strcpy() function, cast both arguments as
  1552.         (far char *) to make sure both pointers get passed as 32-bit
  1553.         pointers, like this :
  1554.  
  1555.             far_strcpy((char far *)name, (char far *)(*acct)[who].acctname);
  1556.  
  1557.         DO NOT IMPLEMENT FAR_STRCPY AS A MACRO!!!!!
  1558.  
  1559.         It is quite possible, and easy, to write small model programs as
  1560.         Falken Doors.  You must use great care to assure that any data
  1561.         gotten via the pointers Falken passes to your program is
  1562.         retrieved as FAR data.
  1563.  
  1564.